package generator

import (
	"bytes"
	"fmt"
	"gitee.com/shifengbin/protoc-gen-error/options"
	"google.golang.org/protobuf/compiler/protogen"
	"google.golang.org/protobuf/proto"
	"os"
	"strings"
	"text/template"
)

type ErrorFunc struct {
	EnumType, ErrType string
	Errors []struct{
		Name string
	}
}

func (ef *ErrorFunc) GenIsFunc() string {
	funcTmp, err := template.New("tmp").Parse(tmp)
	if err != nil {
		panic(err)
	}
	buf := &bytes.Buffer{}
	err = funcTmp.Execute(buf, ef)
	if err != nil {
		panic(err)
	}
	return buf.String()
}

var tmp = `
{{$enumType:=.EnumType}}
{{$errType:=.ErrType}}
{{range .Errors}}
func Is{{.Name}}(e error) bool {
	v, _ := e.(*{{$errType}})
	if v == nil {
		return false
	}
	if err := ConvertToError({{$enumType}}(v.Code)); err != nil {
		return v.Code == err.Code
	}
	return false
}
{{end}}
`

func GenFile(genF *protogen.GeneratedFile, file *protogen.File, a string)  {
	genF.P("// Code generated by protoc-gen-error. DO NOT EDIT!")
	genF.P()
	pkg := fmt.Sprintf("package %s\n", file.GoPackageName)
	genF.P(pkg)

	if a != "" { //通过参数传递进来
		sysErrPackage := protogen.GoImportPath("errors")
		genF.P("var A =", sysErrPackage.Ident("New(\"") , a, "\")")
	}
	genF.P()


	errorPackage    := protogen.GoImportPath("gitee.com/shifengbin/protoc-gen-error/errors") // 自己框架定义的error类型包名

	//生成合法的Go标识符的字符串。
	//如果标识符来自与生成文件不同的包,会将标识符包的导入声明将包含在文件中。
	//name := genF.QualifiedGoIdent(errorPackage.Ident(""))
	//fmt.Fprintf(os.Stderr, "name1:%s\n", name)
	//如果GoIdent类型直接传入P函数,内部会自动调用QualifiedGoIdent,
	//所以说如果使用的是P函数生成代码,一般不会手动调用QualifiedGoIdent, 如果使用的是text模板生成代码,就需要自己手动调用QualifiedGoIdent来生成字符串名称了

	//b := protogen.GoIdent{
	//	GoName:       "b",
	//	GoImportPath: "a/b",
	//}
	//genF.P("var tb=", b) //内部会自动调用QualifiedGoIdent
	////name := genF.QualifiedGoIdent(b) //自动导入a/b包,并返回名称
	////fmt.Fprintf(os.Stderr, "b:%s\n", name)
	//c := protogen.GoIdent{
	//	GoName:       "b",
	//	GoImportPath: "c/b",
	//}
	//genF.P("var tc=", c)
	//name = genF.QualifiedGoIdent(c) //自动导入c/b包,并返回名称, 这个包和上面名称都是b, 这个会返回b1
	//fmt.Fprintf(os.Stderr, "b1:%s\n", name)

	//初始化模板对象
	var funcTmp = ErrorFunc{
		EnumType: genF.QualifiedGoIdent(file.Enums[0].GoIdent),
		ErrType:  genF.QualifiedGoIdent(errorPackage.Ident("Error")),
	}
	for _, enu := range file.Enums {

		//fmt.Fprintf(&buf, "//ENUM %s -- %v \n", v.Desc.Name(), v.GoIdent.GoName)
		genF.P("var errMap=map[", enu.GoIdent, "]*",errorPackage.Ident("Error"), "{")
		for _, value := range enu.Values {
			detail := proto.GetExtension(value.Desc.Options(), options.E_Detail)
			detailErr, ok := detail.(*options.Error)
			if !ok {
				genF.Skip() //不生成文件
				panic(fmt.Sprintf("%s,%s 选项转换失败", enu.Desc.Name(), value.Desc.Name() ))
				return
			}
			if detailErr == nil {
				continue
			}

			genF.P(value.GoIdent, ":", errorPackage.Ident("New("),detailErr.Code, ",\"", detailErr.Msg, "\"),")
			funcTmp.Errors = append(funcTmp.Errors, struct{ Name string }{Name: strings.ReplaceAll(value.GoIdent.GoName, "_", "")})
		}
		genF.P("}")
	}

	//生成转换函数
	genF.P("func ConvertToError( e ", file.Enums[0].GoIdent, ") *", errorPackage.Ident("Error"), "{")
	genF.P("if v, ok := errMap[e]; ok {")
	genF.P("return v")
	genF.P("}")
	genF.P("return nil")
	genF.P("}")



	genF.P(funcTmp.GenIsFunc())


	for _, srv := range file.Services {
		fmt.Fprintf(os.Stderr, "Service:%s--%s\n", srv.GoName, srv.Desc.FullName())
		for _, method := range srv.Methods {
			fmt.Fprintf(os.Stderr, "Method:%s--%s\n", method.GoName, method.Desc.FullName())
			fmt.Fprintf(os.Stderr, "Input:%s\n", genF.QualifiedGoIdent(method.Input.GoIdent))
			fmt.Fprintf(os.Stderr, "Output:%s\n", genF.QualifiedGoIdent(method.Output.GoIdent))
		}
	}
}
